import os
import tensorflow as tf
from tensorflow.keras import datasets, layers, models
import gzip
import numpy as np

'''
python 3.7
tensorflow 2.0.0b0
'''

config = {
    'check_path': "./ckpt1/cp-{epoch:04d}.ckpt",
}


class CNN(object):
    def __init__(self):
        model = models.Sequential()
        # 第1层卷积，卷积核大小为3*3，32个，28*28为待训练图片的大小
        model.add(layers.Conv2D(
            32, (3, 3), activation='relu', input_shape=(28, 28, 1)))
        model.add(layers.MaxPooling2D((2, 2)))
        # 第2层卷积，卷积核大小为3*3，64个
        model.add(layers.Conv2D(64, (3, 3), activation='relu'))
        model.add(layers.MaxPooling2D((2, 2)))
        # 第3层卷积，卷积核大小为3*3，64个
        model.add(layers.Conv2D(64, (3, 3), activation='relu'))

        model.add(layers.Flatten())
        model.add(layers.Dense(64, activation='relu'))
        model.add(layers.Dense(10, activation='softmax'))

        model.summary()

        self.model = model


class DataSource(object):
    """
    读取.npz格式的数据集

    .npz：一个压缩文件，存储多个.npy文件
    使用np.savez()函数可以将多个数组保存到同一个文件中。
    读取.npz文件时使用np.load()函数，返回的是一个类似于字典的对象，因此可以通过数组名作为关键字对多个数组进行访问
    """

    def __init__(self):
        # mnist数据集存储的位置，如何不存在将自动下载
        data_path = os.path.abspath(os.path.dirname(
            __file__)) + './../../data_set_tf2/mnist.npz'
        (train_images, train_labels), (test_images,
                                       test_labels) = datasets.mnist.load_data(path=data_path)
        # 6万张训练图片，1万张测试图片
        train_images = train_images.reshape((60000, 28, 28, 1))
        test_images = test_images.reshape((10000, 28, 28, 1))
        # 像素值映射到 0 - 1 之间
        train_images, test_images = train_images / 255.0, test_images / 255.0

        self.train_images, self.train_labels = train_images, train_labels
        self.test_images, self.test_labels = test_images, test_labels


class DataSourceMnist(object):
    """
    TODO 读取idx3-ubyte.gz格式的数据集，并转换为.npy（Numpy读取专用）格式存储
    """

    data_set_path = './../../data_set/'

    def __init__(self):
        dir_path = os.path.abspath(os.path.dirname(
            __file__)) + self.data_set_path

        files = [
            'train-labels-idx1-ubyte.gz', 'train-images-idx3-ubyte.gz',
            't10k-labels-idx1-ubyte.gz', 't10k-images-idx3-ubyte.gz']
        paths = []
        for i in range(len(files)):
            paths.append(dir_path + files[i])

        with gzip.open(paths[0], 'rb') as TRAIN_LABELS:
            train_labels = np.frombuffer(TRAIN_LABELS.read(), np.uint8, offset=8)
        with gzip.open(paths[1], 'rb') as TRAIN_IMAGES:
            train_images = np.frombuffer(TRAIN_IMAGES.read(), np.uint8, offset=16).reshape(len(train_labels), 28, 28)
        with gzip.open(paths[2], 'rb') as TEST_LABELS:
            test_labels = np.frombuffer(TEST_LABELS.read(), np.uint8, offset=8)
        with gzip.open(paths[3], 'rb') as TEST_IMAGES:
            test_images = np.frombuffer(TEST_IMAGES.read(), np.uint8, offset=16).reshape(len(test_labels), 28, 28)

        # 符合Mnist规范的数据集中，6/7是训练图片，1/7是测试图片。
        train_images = train_images.reshape((60000, 28, 28, 1))
        test_images = test_images.reshape((10000, 28, 28, 1))

        # 像素值映射到 0 - 1 之间
        train_images, test_images = train_images / 255.0, test_images / 255.0

        self.train_images, self.train_labels = train_images, train_labels
        self.test_images, self.test_labels = test_images, test_labels


class Train:
    def __init__(self):
        self.cnn = CNN()
        self.data = DataSourceMnist()

    def train(self):
        check_path = './ckpt1/cp-{epoch:05d}.ckpt'
        # check_path = config['check_path']
        # period 每隔5epoch保存一次
        save_model_cb = tf.keras.callbacks.ModelCheckpoint(
            check_path, save_weights_only=True, verbose=1, period=5)

        self.cnn.model.compile(optimizer='adam',
                               loss='sparse_categorical_crossentropy',
                               metrics=['accuracy'])
        self.cnn.model.fit(self.data.train_images, self.data.train_labels,
                           epochs=5, callbacks=[save_model_cb])

        test_loss, test_acc = self.cnn.model.evaluate(
            self.data.test_images, self.data.test_labels)
        print("准确率: %.4f，共测试了%d张图片 " % (test_acc, len(self.data.test_labels)))


if __name__ == "__main__":
    app = Train()
    app.train()
